// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataChunker.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Runtime
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    /// <summary>
    /// Chunking datagrams for handling large payloads over a size-limited transport.
    /// </summary>
    /// <remarks>
    /// Payloads larger than the max datagram size need to be split and reconstituted on
    /// the receiving end. We add our own header which includes a 4-byte ID, a 2-byte
    /// count of the number of "chunks" to expect, and a 2-byte zero-based number of the
    /// particular chunk.
    /// </remarks>
    public class DataChunker
    {
        /// <summary>
        /// Number of bytes for our chunking header.
        /// </summary>
        public const int HeaderSize = 8 /* ID */ + 2 /* count */ + 2 /* number */;

        /// <summary>
        /// Maximum size of a datagram.
        /// </summary>
        public readonly int MaxDatagramSize;

        /// <summary>
        /// Number of bytes remaining for payload being sent.
        /// </summary>
        public readonly int PayloadSize;

        /// <summary>
        /// Internal buffer used to construct chunked datagrams being sent.
        /// </summary>
        private byte[] buffer;

        /// <summary>
        /// Initializes a new instance of the <see cref="DataChunker"/> class.
        /// </summary>
        /// <param name="maxDatagramSize">Maximum size of a datagram.</param>
        public DataChunker(int maxDatagramSize)
        {
            this.MaxDatagramSize = maxDatagramSize;
            this.PayloadSize = this.MaxDatagramSize - HeaderSize;
            this.buffer = new byte[this.MaxDatagramSize];
        }

        /// <summary>
        /// Break the payload into chunks as necessary, encoded with an ID.
        /// </summary>
        /// <param name="id">Unique ID across a reasonable time span.</param>
        /// <param name="payload">Binary payload to be sent.</param>
        /// <param name="length">Length of payload within given buffer.</param>
        /// <returns>Chunks as pairs of byte[], length.</returns>
        public IEnumerable<Tuple<byte[], int>> GetChunks(long id, byte[] payload, int length)
        {
            var count = (ushort)(length / this.MaxDatagramSize + 1);

            var stream = new MemoryStream(this.buffer);
            var writer = new BinaryWriter(stream);
            writer.Write(id);
            writer.Write(count);

            for (ushort num = 0; num < count; num++)
            {
                writer.Write(num);
                var size = (num < count - 1 ? this.PayloadSize : length % this.PayloadSize);
                Array.Copy(payload, num * this.PayloadSize, this.buffer, HeaderSize, size);
                yield return new Tuple<byte[], int>(this.buffer, size + HeaderSize);
                stream.Position = stream.Position - 2; // prior to num field
            }
        }
    }
}
